package com.self.cloudmall.search.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.self.cloudmall.common.constant.EsConstant;
import com.self.cloudmall.common.dto.ElasticSkuDto;
import com.self.cloudmall.search.config.ElasticConfig;
import com.self.cloudmall.search.service.SearchService;
import com.self.cloudmall.search.vo.SearchParam;
import com.self.cloudmall.search.vo.SearchResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.List;

/**
 * @version v1.0
 * @ClassName: SearchServiceImpl
 * @Description:
 */
@Slf4j
@Service
public class SearchServiceImpl  implements SearchService {

    @Autowired
    private RestHighLevelClient elasticClient;

    @Override
    public SearchResult search(SearchParam searchParam) {
        SearchResult searchResult = null;
        try {
            log.info("商品搜索请求参数：{}",JSONObject.toJSONString(searchParam));
            SearchRequest searchRequest = buildSearchRequest(searchParam);
            SearchResponse searchResponse = elasticClient.search(searchRequest, ElasticConfig.COMMON_OPTIONS);
            searchResult = buildSearchResult(searchResponse,searchParam);
            log.info("商品搜索返回值：{}",JSONObject.toJSONString(searchResult));
        } catch (Exception e) {
            log.error("商品搜索异常：{}",e);
        }
        return searchResult;
    }

    private SearchResult buildSearchResult(SearchResponse searchResponse,SearchParam searchParam) {
        SearchResult searchResult = new SearchResult();
        SearchHits hits = searchResponse.getHits();
        //总记录数
        long total = hits.getTotalHits().value;
        searchResult.setTotalRecord(total);
        //当前页
        Integer pageNum = searchParam.getPageNum();
        searchResult.setPageNum(pageNum);
        //总页数
        int totalPage = (int)Math.ceil(total / EsConstant.ES_PAGE_SIZE);
        searchResult.setTotalPage(totalPage);
        List<String> pageNos = Lists.newArrayList();
        if (totalPage < 5){
            for (int i = 1; i <= totalPage ; i++){
                pageNos.add(i+"");
            }
        }else{
            for (int i = 1; i <= 3 ; i++){
                pageNos.add(i+"");
            }
            pageNos.add("...");
            pageNos.add(totalPage+"");
        }
        searchResult.setPageNos(pageNos);
        SearchHit[] searchHits = hits.getHits();
        if (searchHits != null && searchHits.length > 0){
            List<ElasticSkuDto> skuDtos = Lists.newArrayList();
            for (SearchHit hit : searchHits){
                String sourceAsString = hit.getSourceAsString();
                ElasticSkuDto elasticSkuDto = JSONObject.parseObject(sourceAsString, ElasticSkuDto.class);
                if (StringUtils.isNotEmpty(searchParam.getKeyword())){
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    String field = skuTitle.getFragments()[0].string();
                    elasticSkuDto.setSkuTitle(field);
                }
                skuDtos.add(elasticSkuDto);
            }
            searchResult.setProducts(skuDtos);
        }
        ParsedLongTerms brandId_agg = searchResponse.getAggregations().get("brandId_agg");
        List<SearchResult.BrandVo> brandVos = Lists.newArrayList();
        for (Terms.Bucket bucket : brandId_agg.getBuckets()){
            Number keyAsNumber = bucket.getKeyAsNumber();
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
            brandVo.setBrandId(keyAsNumber.longValue());
            ParsedStringTerms brandName_agg = bucket.getAggregations().get("brandName_agg");
            Terms.Bucket brandName_bucket = brandName_agg.getBuckets().get(0);
            brandVo.setBrandName(brandName_bucket.getKeyAsString());
            ParsedStringTerms brandImg_agg = bucket.getAggregations().get("brandImg_agg");
            Terms.Bucket brandImg_bucket = brandImg_agg.getBuckets().get(0);
            brandVo.setBrandImg(brandImg_bucket.getKeyAsString());
            brandVos.add(brandVo);
        }
        searchResult.setBrands(brandVos);

        ParsedLongTerms catalog_agg = searchResponse.getAggregations().get("catalog_agg");
        List<SearchResult.CatalogVo> catalogVos = Lists.newArrayList();
        for (Terms.Bucket bucket : catalog_agg.getBuckets()){
            Number keyAsNumber = bucket.getKeyAsNumber();
            SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
            catalogVo.setCatalogId(keyAsNumber.longValue());
            ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
            Terms.Bucket catalog_name_bucket = catalog_name_agg.getBuckets().get(0);
            catalogVo.setCatalogName(catalog_name_bucket.getKeyAsString());
            catalogVos.add(catalogVo);
        }
        searchResult.setCatalogs(catalogVos);

        ParsedNested attrs_agg = searchResponse.getAggregations().get("attrs_agg");
        ParsedLongTerms attrs_id_agg = attrs_agg.getAggregations().get("attrs_id_agg");
        List<SearchResult.AttrVo> attrVos = Lists.newArrayList();
        for (Terms.Bucket bucket : attrs_id_agg.getBuckets()){
            Number keyAsNumber = bucket.getKeyAsNumber();
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            attrVo.setAttrId(keyAsNumber.longValue());
            ParsedStringTerms attrs_name_agg = bucket.getAggregations().get("attrs_name_agg");
            Terms.Bucket attrs_name_bucket = attrs_name_agg.getBuckets().get(0);
            attrVo.setAttrName(attrs_name_bucket.getKeyAsString());
            ParsedStringTerms attrs_value_agg = bucket.getAggregations().get("attrs_value_agg");
            List<String> vals = Lists.newArrayList();
            for (Terms.Bucket attrs_value_bucket : attrs_value_agg.getBuckets()){
                vals.add(attrs_value_bucket.getKeyAsString());
            }
            attrVo.setAttrValue(vals);
            attrVos.add(attrVo);
        }
        searchResult.setAttrs(attrVos);
        return searchResult;
    }

    private SearchRequest buildSearchRequest(SearchParam searchParam) {
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices(EsConstant.PRODUCT_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (StringUtils.isNotEmpty(searchParam.getKeyword())){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
        }
        if (searchParam.getCatalog3Id() != null){
            boolQuery.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));
        }
        if (!CollectionUtils.isEmpty(searchParam.getBrandId())){
            boolQuery.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
        }
        if (searchParam.getHasStock() != null){
            boolQuery.filter(QueryBuilders.termQuery("hasStock",searchParam.getHasStock()));
        }
        if (StringUtils.isNotEmpty(searchParam.getSkuPrice())){
            String[] split = searchParam.getSkuPrice().split("_");
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            if (split[0] != null && StringUtils.isNotEmpty(split[0].trim())){
                rangeQuery.gte(split[0]);
            }
            if (split[1] != null && StringUtils.isNotEmpty(split[1].trim())){
                rangeQuery.lte(split[1]);
            }
            boolQuery.filter(rangeQuery);
        }
        if (!CollectionUtils.isEmpty(searchParam.getAttrs())){
            for (String attr : searchParam.getAttrs()){
                String[] split = attr.split("_");
                if (split != null && split.length == 2){
                    BoolQueryBuilder boolQueryBuilder = null;
                    if (StringUtils.isNotEmpty(split[0])){
                        boolQueryBuilder = QueryBuilders.boolQuery()
                                .must(QueryBuilders.termQuery("attrs.attrId", split[0]));
                    }
                    if (StringUtils.isNotEmpty(split[1])){
                        String[] vals = split[1].split(":");
                        if (vals != null && vals.length > 0){
                            boolQueryBuilder.must(QueryBuilders.termsQuery("attrs.attrValue",vals));
                        }
                    }
                    boolQuery.filter(QueryBuilders.nestedQuery("attrs",boolQueryBuilder, ScoreMode.None));
                }
            }
        }
        searchSourceBuilder.query(boolQuery);
        //构建聚合计算
        buildAggs(searchSourceBuilder);
        //构建排序
        buildSort(searchParam,searchSourceBuilder);
        //高亮显示
        if (StringUtils.isNotEmpty(searchParam.getKeyword())) {
            HighlightBuilder highlight = new HighlightBuilder();
            highlight.field("skuTitle");
            highlight.preTags("<b style='color:red'>");
            highlight.postTags("</b>");
            searchSourceBuilder.highlighter(highlight);
        }
        //分页
        searchSourceBuilder.from((searchParam.getPageNum() - 1) * EsConstant.ES_PAGE_SIZE);
        searchSourceBuilder.size(EsConstant.ES_PAGE_SIZE);
        log.info("构建DSL语句：{}",searchSourceBuilder.toString());
        searchRequest.source(searchSourceBuilder);
        return searchRequest;
    }

    private void buildSort(SearchParam searchParam, SearchSourceBuilder searchSourceBuilder) {
        if (StringUtils.isNotEmpty(searchParam.getSort())){
            String[] split = searchParam.getSort().split("_");
            if (StringUtils.isNotEmpty(split[0]) && StringUtils.isNotEmpty(split[1])){
                searchSourceBuilder.sort(split[0], SortOrder.DESC.toString().equalsIgnoreCase(split[1])?SortOrder.DESC:SortOrder.ASC);
            }
        }
    }

    private void buildAggs(SearchSourceBuilder searchSourceBuilder) {
        searchSourceBuilder.aggregation(AggregationBuilders.terms("brandId_agg").field("brandId").size(50)
                .subAggregation(AggregationBuilders.terms("brandName_agg").field("brandName").size(1))
                .subAggregation(AggregationBuilders.terms("brandImg_agg").field("brandImg").size(1)));
        searchSourceBuilder.aggregation(AggregationBuilders.terms("catalog_agg").field("catalogId").size(50)
                .subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1)));
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrs_agg","attrs")
                .subAggregation(AggregationBuilders.terms("attrs_id_agg").field("attrs.attrId").size(50)
                        .subAggregation(AggregationBuilders.terms("attrs_name_agg").field("attrs.attrName").size(1))
                        .subAggregation(AggregationBuilders.terms("attrs_value_agg").field("attrs.attrValue").size(20))));
    }
}
